package com.bkbw.live.battle.test;
import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.badlogic.gdx.ai.GdxAI;
import com.badlogic.gdx.ai.btree.BehaviorTree;
import com.badlogic.gdx.ai.btree.LeafTask;
import com.badlogic.gdx.ai.btree.Task;
import com.badlogic.gdx.ai.btree.branch.Parallel;
import com.badlogic.gdx.ai.btree.branch.Selector;
import com.badlogic.gdx.ai.btree.branch.Sequence;
import com.badlogic.gdx.ai.pfa.Connection;
import com.badlogic.gdx.ai.pfa.DefaultConnection;
import com.badlogic.gdx.ai.pfa.DefaultGraphPath;
import com.badlogic.gdx.ai.pfa.GraphPath;
import com.badlogic.gdx.ai.pfa.Heuristic;
import com.badlogic.gdx.ai.pfa.indexed.IndexedAStarPathFinder;
import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph;
import com.badlogic.gdx.utils.Array;


public class testgdxai {
	
	public static class Dog{
		public BehaviorTree<Dog> behaviorTree;
		public void log (String msg) {
			GdxAI.getLogger().info(" ", msg);
		}

		public void brainLog (String msg) {
			GdxAI.getLogger().info(" ", msg);
		}
		
		public void bark() {
			log("wang wang");
		}
		

		public void startWalking () {
			log("Let's find a nice tree");
		}

		public void randomlyWalk () {
			log("SNIFF SNIFF - Dog walks randomly around!");
		}

		public void stopWalking () {
			log("This tree smells good :)");
		}		
	}
	
	public static class Task1 extends LeafTask<Dog>{
		int id;
		public Task1(int i) {
			// TODO Auto-generated constructor stub
			id = i;
		}
		@Override
		public com.badlogic.gdx.ai.btree.Task.Status execute() {
			System.out.println("task1 succ"+" id " + id);
			return Status.SUCCEEDED;
		}

		@Override
		protected Task<Dog> copyTo(Task<Dog> paramTask) {
			// TODO Auto-generated method stub
			return null;
		}		
	}
	
	public static class Task2 extends LeafTask<Dog>{
		int id;
		public Task2(int i) {
			// TODO Auto-generated constructor stub
			id = i;
		}
		@Override
		public com.badlogic.gdx.ai.btree.Task.Status execute() {
			System.out.println("task2 run"+" id " + id);
			return Status.RUNNING;
		}

		@Override
		protected Task<Dog> copyTo(Task<Dog> paramTask) {
			// TODO Auto-generated method stub
			return null;
		}		
	}
	
	public static class Task3 extends LeafTask<Dog>{
		int id;
		public Task3(int i) {
			// TODO Auto-generated constructor stub
			id = i;
		}
		@Override
		public com.badlogic.gdx.ai.btree.Task.Status execute() {
			System.out.println("task3 fail "+" id " + id);
			return Status.FAILED;
		}

		@Override
		protected Task<Dog> copyTo(Task<Dog> paramTask) {
			// TODO Auto-generated method stub
			return null;
		}		
	}
	
	@Test 
	public void testBehaviorTree() {
		Dog dog = new Dog();
		
		Selector<Dog> selector = new Selector<Dog>();

		Parallel<Dog> parallel = new Parallel<Dog>();
		selector.addChild(parallel);

		parallel.addChild(new Task1(11));
		parallel.addChild(new Task2(12));
		parallel.addChild(new Task3(13));

		//Sequence<Dog> sequence = new Sequence<Dog>();
		Selector<Dog> sequence = new Selector<Dog>();
		selector.addChild(sequence);

		
		sequence.addChild(new Task1(21));
		sequence.addChild(new Task2(22));
		sequence.addChild(new Task3(23));
		
		BehaviorTree<Dog> bt = new BehaviorTree<testgdxai.Dog>(selector, dog);
		for (int i = 0; i < 10; i++) {
			System.out.println("------------loop----------"+i);
			bt.step();
			if (i == 3) {
				bt.cancel();
			}
		}
		
		System.out.println(BrainMessage.YES.value());
	}
	
	public enum BrainMessage {
		MOVE_STOP(0),YES(1),NO(2);
		int value;
		int value() {
			return value;
		}
		private BrainMessage(int val) {
			// TODO Auto-generated constructor stub
			value = val;
		}
	}

	@Test
	public void searchNodePath_WhenSearchingAdjacentTile_ExpectedOuputPathLengthEquals2 () {
		// @off - disable libgdx formatter
		final String graphDrawing =
				"..........\n" +
				"..........\n" +
				"..........";
		// @on - enable libgdx formatter

		final MyGraph graph = createGraphFromTextRepresentation(graphDrawing);

		final IndexedAStarPathFinder<MyNode> pathfinder = new IndexedAStarPathFinder<MyNode>(graph);

		final GraphPath<MyNode> outPath = new DefaultGraphPath<>();

		// @off - disable libgdx formatter
		// 0123456789
		// .......... 0
		// .....S.... 10
		// .....E.... 20
		// @on - enable libgdx formatter
		final boolean searchResult1 = pathfinder.searchNodePath(graph.nodes.get(15), graph.nodes.get(25), new ManhattanDistance(),
			outPath);

		Assert.assertTrue("Unexpected search result", searchResult1);
		Assert.assertEquals("Unexpected number of nodes in path", 2, outPath.getCount());

		// @off - disable libgdx formatter
		// 0123456789
		// .......... 0
		// .....SE... 10
		// .......... 20
		// @on - enable libgdx formatter
		outPath.clear();
		final boolean searchResult2 = pathfinder.searchNodePath(graph.nodes.get(15), graph.nodes.get(16), new ManhattanDistance(),
			outPath);

		Assert.assertTrue("Unexpected search result", searchResult2);
		Assert.assertEquals("Unexpected number of nodes in path", 2, outPath.getCount());

		// @off - disable libgdx formatter
		// 0123456789
		// .......... 0
		// ....ES.... 10
		// .......... 20
		// @on - enable libgdx formatter
		outPath.clear();
		final boolean searchResult3 = pathfinder.searchNodePath(graph.nodes.get(15), graph.nodes.get(14), new ManhattanDistance(),
			outPath);

		Assert.assertTrue("Unexpected search result", searchResult3);
		Assert.assertEquals("Unexpected number of nodes in path", 2, outPath.getCount());

		// @off - disable libgdx formatter
		// 0123456789
		// .....E.... 0
		// .....S.... 10
		// .......... 20
		// @on - enable libgdx formatter
		outPath.clear();
		final boolean searchResult4 = pathfinder.searchNodePath(graph.nodes.get(15), graph.nodes.get(5), new ManhattanDistance(),
			outPath);

		Assert.assertTrue("Unexpected search result", searchResult4);
		Assert.assertEquals("Unexpected number of nodes in path", 2, outPath.getCount());
	}

	@Test
	public void searchNodePath_WhenSearchCanHitDeadEnds_ExpectedOuputPathFound () {
		// @off - disable libgdx formatter
		final String graphDrawing =
			".#.#.......#..#...............\n" +
			".#............#.....#..#####..\n" +
			"...#.#######..#.....#.........\n" +
			".#.#.#........#.....########..\n" +
			".###.#....#####.....#......##.\n" +
			".#...#....#.........#...##....\n" +
			".#####....#.........#....#....\n" +
			".#........#.........#....#####\n" +
			".####....##.........#......#..\n" +
			"....#...............#......#..";
		// @on - enable libgdx formatter

		final MyGraph graph = createGraphFromTextRepresentation(graphDrawing);

		final IndexedAStarPathFinder<MyNode> pathfinder = new IndexedAStarPathFinder<>(graph);

		final GraphPath<MyNode> outPath = new DefaultGraphPath<>();

		// @off - disable libgdx formatter
		// 012345678901234567890123456789
		// S#.#.......#..#............... 0
		// .#............#.....#..#####.. 30
		// ...#.#######..#.....#......... 60
		// .#.#.#........#.....########.. 90
		// .###.#....#####.....#......##. 120
		// .#...#....#.........#...##.... 150
		// .#####....#.........#....#.... 180
		// .#E.......#.........#....##### 210
		// .####....##.........#......#.. 240
		// ....#...............#......#.. 270
		// @on - enable libgdx formatter
		final boolean searchResult = pathfinder.searchNodePath(graph.nodes.get(0), graph.nodes.get(212), new ManhattanDistance(),
			outPath);

		Assert.assertTrue("Unexpected search result", searchResult);
		Assert.assertEquals("Unexpected number of nodes in path", 32, outPath.getCount());
	}

	@Test
	public void searchNodePath_WhenDestinationUnreachable_ExpectedNoOuputPathFound () {
		// @off - disable libgdx formatter
		final String graphDrawing =
			".....#....\n" +
			".....#....\n" +
			".....#....";
		// @on - enable libgdx formatter

		final MyGraph graph = createGraphFromTextRepresentation(graphDrawing);

		final IndexedAStarPathFinder<MyNode> pathfinder = new IndexedAStarPathFinder<>(graph);

		final GraphPath<MyNode> outPath = new DefaultGraphPath<>();

		// @off - disable libgdx formatter
		// 0123456789
		// S....#...E 0
		// .....#.... 10
		// .....#.... 20
		// @on - enable libgdx formatter
		final boolean searchResult = pathfinder.searchNodePath(graph.nodes.get(0), graph.nodes.get(9), new ManhattanDistance(),
			outPath);

		Assert.assertFalse("Unexpected search result", searchResult);
	}

	private static MyGraph createGraphFromTextRepresentation (final String graphTextRepresentation) {
		final String[][] tiles = createStringTilesFromGraphTextRepresentation(graphTextRepresentation);

		final int numRows = tiles[0].length;
		final int numCols = tiles.length;

		final MyNode[][] nodes = new MyNode[numCols][numRows];
		final Array<MyNode> indexedNodes = new Array<>(numCols * numRows);

		int index = 0;
		for (int y = 0; y < numRows; y++) {
			for (int x = 0; x < numCols; x++, index++) {
				nodes[x][y] = new MyNode(index, x, y, 4);
				indexedNodes.add(nodes[x][y]);
			}
		}

		for (int y = 0; y < numRows; y++, index++) {
			for (int x = 0; x < numCols; x++, index++) {
				if (tiles[x][y].equals("#")) {
					continue;
				}

				if (x - 1 >= 0 && tiles[x - 1][y].equals(".")) {
					nodes[x][y].getConnections().add(new DefaultConnection<MyNode>(nodes[x][y], nodes[x - 1][y]));
				}

				if (x + 1 < numCols && tiles[x + 1][y].equals(".")) {
					nodes[x][y].getConnections().add(new DefaultConnection<MyNode>(nodes[x][y], nodes[x + 1][y]));
				}

				if (y - 1 >= 0 && tiles[x][y - 1].equals(".")) {
					nodes[x][y].getConnections().add(new DefaultConnection<MyNode>(nodes[x][y], nodes[x][y - 1]));
				}

				if (y + 1 < numRows && tiles[x][y + 1].equals(".")) {
					nodes[x][y].getConnections().add(new DefaultConnection<MyNode>(nodes[x][y], nodes[x][y + 1]));
				}

			}
		}

		return new MyGraph(indexedNodes);
	}

	private static String[][] createStringTilesFromGraphTextRepresentation (final String graphTextRepresentation) {
		final String[] rows = graphTextRepresentation.split("\n");

		final int numRows = rows.length;
		final int numCols = rows[0].length();

		final String[][] tiles = new String[numCols][numRows];

		for (int y = 0; y < numRows; y++) {
			final String row = rows[y];
			for (int x = 0; x < numCols; x++) {
				tiles[x][y] = "" + row.charAt(x);
			}
		}

		return tiles;
	}

	private static class MyNode {

		private final int index;
		private final int x;
		private final int y;
		private final Array<Connection<MyNode>> connections;

		public MyNode (final int index, final int x, final int y, final int capacity) {
			this.index = index;
			this.x = x;
			this.y = y;
			this.connections = new Array<>(capacity);
		}

		public int getIndex () {
			return index;
		}

		public Array<Connection<MyNode>> getConnections () {
			return connections;
		}

		@Override
		public String toString () {
			return "IndexedNodeFake [index=" + index + ", x=" + x + ", y=" + y + ", connections=" + connections + "]";
		}

	}

	private static class MyGraph implements IndexedGraph<MyNode> {

		protected Array<MyNode> nodes;

		public MyGraph (Array<MyNode> nodes) {
			this.nodes = nodes;
		}

		@Override
		public int getIndex (MyNode node) {
			return node.getIndex();
		}

		@Override
		public Array<Connection<MyNode>> getConnections (MyNode fromNode) {
			return fromNode.getConnections();
		}

		@Override
		public int getNodeCount () {
			return nodes.size;
		}
	}

	private static class ManhattanDistance implements Heuristic<MyNode> {
		@Override
		public float estimate (final MyNode node, final MyNode endNode) {
			return Math.abs(endNode.x - node.x) + Math.abs(endNode.y - node.y);
		}
	}

}
